/**
 * Find nearby grid with most populated humans and infect them!
 */
package jzombies;

import java.util.ArrayList;
import java.util.List;

import repast.simphony.context.Context;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.query.space.grid.GridCell;
import repast.simphony.query.space.grid.GridCellNgh;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.SpatialMath;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.continuous.NdPoint;
import repast.simphony.space.graph.Network;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.util.ContextUtils;
import repast.simphony.util.SimUtilities;

/**
 * @author grace
 *
 */
public class Zombie {

	private ContinuousSpace <Object> space; // use of floating point
	private Grid<Object> grid; // neighborhood proximity 
	private boolean moved; 
	
	public Zombie(ContinuousSpace<Object> space, Grid<Object> grid) {
		this.space = space;
		this.grid = grid;
	}
	
	// during each iteration, zombie look for nearby grid with most humans
	// want to call step() every iteration of simulation
	// ScheduledMethod is initiated upon instance of Zombie class
	// start at tick (timestamp) 1 and every tic thereafter
	@ScheduledMethod(start = 1, interval = 1)
	public void step() {
		// get grid location of this Zombie
		GridPoint pt = grid.getLocation(this);
		
		// use GridCellNgh class to create GridCells for
		// the surrounding neighborhood.
		// list of 8 neighboring cells that contain humans
		// look at humans only
		GridCellNgh<Human> nghCreator = new GridCellNgh<Human>(grid, pt,
			Human.class, 1, 1);
		// create corresponding list		
		List<GridCell <Human>> gridCells = nghCreator.getNeighborhood(true);
		
		// without shuffle, zombies will always move in the same direction
		// when all cells are equal
		SimUtilities.shuffle(gridCells, RandomHelper.getUniform());
		
		GridPoint pointWithMostHumans = null;
		int maxCount = -1;
		for (GridCell<Human> cell : gridCells) {
			if (cell.size() > maxCount) {
				pointWithMostHumans = cell.getPoint();
				maxCount = cell.size();
			}
		}
		
		// move zombie to the desired cell
		moveTowards(pointWithMostHumans);
		
		// infect human (if there's any in the area)
		infect();
	}
	
	// once we've located grid with most humans, move the zombie to it
	public void moveTowards(GridPoint pt) {
		// only move if we are not already in this grid location
		if (!pt.equals(grid.getLocation(this))) {
			// convert GridPoint to NdPoint to use in ContinuousSpace
			NdPoint myPoint = space.getLocation(this); // N-dimensional point in space
			NdPoint otherPoint = new NdPoint(pt.getX(), pt.getY());
			double angle = SpatialMath.calcAngleFor2DMovement(space, 
					myPoint, otherPoint);
			space.moveByVector(this, 1, angle, 0);
			// convert back to 'int' for GridPoint
			myPoint = space.getLocation(this);
			grid.moveTo(this, (int)myPoint.getX(), (int)myPoint.getY());
			moved = true;
		}
	}
	
	// infect humans
	public void infect() {
		GridPoint pt = grid.getLocation(this);
		List<Object> humans = new ArrayList<Object>();
		for (Object obj : grid.getObjectsAt(pt.getX(), pt.getY())) {
			if (obj instanceof Human) {
				humans.add(obj);
			}
		}
		if (humans.size()>0) {
			int index = RandomHelper.nextIntFromTo(0, humans.size()-1);
			Object obj = humans.get(index);
			NdPoint spacePt = space.getLocation(obj);
			Context<Object> context = ContextUtils.getContext(obj);
			// replace human with zombie
			context.remove(obj);
			Zombie zombie = new Zombie(space, grid);
			context.add(zombie);
			space.moveTo(zombie, spacePt.getX(), spacePt.getY());
			grid.moveTo(zombie, pt.getX(), pt.getY());
			
			Network<Object> net = (Network<Object>) context.getProjection("infection network");
			net.addEdge(this, zombie);
		}
	}
	
}
